﻿using System;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using ECE454P1;
using main_test;

namespace ECE454P1
{

    public enum message_type { data, message }
    public enum State { connected, disconnected, unknown }

    [Serializable]
    struct ipc_message
    {
        public IPAddress ip_source;
        public message_type message_type;
        public string message;
        public string file_name;
        public string file_hash;
        public int number_of_chunks;
        public int chunk_number;
        public bool partial_chunk;
        public uint port_source;
    }

    class TcpListen
    {
        private TcpListener server;
        private Thread server_thread;
        private Peers peers;
        private Status status;
        private Peer local_peer;

        public TcpListen(ref Peers peers, ref Status status, ref Peer local_peer)
        {
            this.status = status;
            this.peers = peers;
            this.local_peer = local_peer;
            this.server = new TcpListener(IPAddress.Any, 10030);
            this.server_thread = new Thread(new ThreadStart(listen_thread));
            this.server_thread.Start();
        }

        public void listen_thread()
        {
            this.server.Start();
            while (true)
            {
                TcpClient client = this.server.AcceptTcpClient();
                Thread client_thread = new Thread(new ParameterizedThreadStart(communication_thread));
                client_thread.Start(client);
            }
        }

        private void communication_thread(object tcp_client)
        {
            TcpClient client = (TcpClient)tcp_client;
            NetworkStream stream = client.GetStream();

            int i = 0;
            int bytes_read = 0;
            Byte[] r_message = new Byte[Constants.chunkSize];
            Byte[] length_prefix = new Byte[8];
            IFormatter formatter = new BinaryFormatter();

            //
            //Read length prefix
            //
            do
            {
                bytes_read += stream.Read(length_prefix, bytes_read, length_prefix.Length - bytes_read);
            } while (bytes_read < 8);

            long length_prefix_long = BitConverter.ToInt64(length_prefix, 0);
            bytes_read = 0;
            //
            //Read message header
            //
            do
            {
                bytes_read += stream.Read(r_message, bytes_read, r_message.Length - bytes_read);
            } while (bytes_read<length_prefix_long);

            MemoryStream ms = new MemoryStream(r_message);
            ipc_message header_message = (ipc_message)formatter.Deserialize(ms);

            //
            //Acknowledge message received
            //
            stream.WriteByte(00);

            //Data type message
            if (header_message.message_type == message_type.data)
            {
                this.status.update_peer(this.local_peer, header_message, header_message.chunk_number);
                FileStream file_stream;

                //
                //Write file to the partial chunks folder
                //
                file_stream = new FileStream("files/partial_chunks/" + header_message.file_hash + "_" + header_message.file_name + "_" + header_message.chunk_number.ToString().PadLeft((header_message.number_of_chunks.ToString().Length), '0') + "_" + header_message.number_of_chunks, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

                while ((i = stream.Read(r_message, 0, r_message.Length)) != 0)
                {
                    file_stream.Write(r_message, 0, i);
                }
                file_stream.Close();

                //
                //If last needed chunk, compile the file.
                //
                int index = 0;
                foreach (string file_hash in this.status.file_hash)
                {
                    if (header_message.file_hash == file_hash)
                    {
                        if (this.status.fractionPresentLocally(index) >= 1)
                        {
                            Console.WriteLine("SERVER: Compile file.....");
                            Console.WriteLine(header_message.file_name);

                            file_stream = new FileStream("files/" + header_message.file_name, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);

                            DirectoryInfo di = new DirectoryInfo("files/partial_chunks");
                            FileInfo[] fi = di.GetFiles();

                            FileStream copy_stream;
                            foreach (FileInfo file in fi)
                            {
                                if (file.Name.Contains(file_hash))
                                {
                                    copy_stream = new FileStream("files/partial_chunks/" + file.Name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                                    Console.WriteLine("OPENING {0}", "files/partial_chunks/" + file.Name);
                                    copy_stream.Read(r_message, 0, Convert.ToInt32(file.Length));
                                    file_stream.Write(r_message, 0, Convert.ToInt32(file.Length));
                                    copy_stream.Close();
                                }
                            }
                            file_stream.Close();
                        }
                    }
                    index++;
                }
            }
            //
            //Message type message
            //
            else if (header_message.message_type == message_type.message)
            {
                //
                //Join message
                //
                if (header_message.message == "join")
                {
                    foreach (Peer p in this.peers.peers)
                    {
                        if (header_message.ip_source.Equals(p.ipaddress) && header_message.port_source == p.port)
                        {
                            Console.WriteLine("CHANGING STATE {0} to {1}", p.state.ToString(), State.connected.ToString());
                            p.state = State.connected;
                            Thread thread = new Thread(new ParameterizedThreadStart(this.local_peer.push_files));
                            thread.Start(p);
                            break;
                        }
                    }
                }
                //
                //Leave Message
                //
                else if (header_message.message == "leave")
                {
                    foreach (Peer p in this.peers.peers)
                    {
                        if (header_message.ip_source.Equals(p.ipaddress) && header_message.port_source == p.port)
                        {
                            Console.WriteLine("CHANGING STATE {0} to {1}", p.state.ToString(), State.disconnected.ToString());
                            p.state = State.disconnected;
                            break;
                        }
                    }
                }
                //
                //Query request
                //
                else if (header_message.message == "query")
                {
                    formatter.Serialize(stream, this.status);
                }
            }

            stream.Close();
            client.Close();
            return;
        }
    }

    // Peer and Status are the classes we really care about
    // Peers is a container; feel free to do a different container
    class Peer
    {
        public uint port;
        public IPAddress ipaddress;
        public List<Peer> peers;
        public State state;
        public Status status;
        public Thread thread;

        private TcpClient _client;
        private NetworkStream _network_stream;
        private bool _is_connected = false;

        public int insert(string fileName)
        {
            if (File.Exists(fileName))
            {
                string insert_hash = Constants.md5_hash(fileName);

                foreach (string hash in this.status.file_hash)
                {
                    if (hash == insert_hash)
                    {
                        Console.WriteLine("INSERT: File Already Exists");
                        return Constants.errOK;
                    }
                }

                FileInfo file = new FileInfo(fileName);
                if (File.Exists("files/" + fileName))
                {
                    int i = 0;
                    while (File.Exists("files/" + Path.GetFileNameWithoutExtension(file.Name).Replace('_', '-') + "-" + i + file.Extension))
                    {
                        i++;
                    }
                    File.Copy(fileName, "files/" + Path.GetFileNameWithoutExtension(file.Name).Replace('_', '-') + "-" + i + file.Extension);
                    this.status.file_name[this.status.numberOfFiles()] = Path.GetFileNameWithoutExtension(file.Name).Replace('_', '-') + "-" + i + file.Extension;
                }
                else
                {
                    File.Copy(fileName, "files/" + file.Name.Replace('_', '-'));
                    this.status.file_name[this.status.numberOfFiles()] = file.Name.Replace('_', '-');
                }
                this.status.local[this.status.numberOfFiles()] = 1;
                this.status.system[this.status.numberOfFiles()] = 1;
                this.status.leastReplication[this.status.numberOfFiles()] = 0;
                this.status.weightedLeastReplication[this.status.numberOfFiles()] = 1;

                this.status.file_hash[this.status.numberOfFiles()] = Constants.md5_hash(file.FullName);
                this.status.file_size[this.status.numberOfFiles()] = file.Length;
                this.status.number_of_chunks[this.status.numberOfFiles()] = (int)Math.Ceiling(((double)file.Length) / ((double)Constants.chunkSize));
                this.status.chunks_present[this.status.numberOfFiles()] = new List<int>();
                for (int c = 0; c < this.status.number_of_chunks[this.status.numberOfFiles()]; c++)
                {
                    this.status.chunks_present[this.status.numberOfFiles()].Add(c);
                    Console.WriteLine("Adding chunk {0}", c);
                }
                this.status.numFiles++;

                this.join();

                return Constants.errOK;
            }
            return Constants.errUnknownFatal;
        }

        public int query(ref Status status)
        {
            Console.WriteLine("Running Query");
            foreach (Peer p in this.peers)
            {
                if (p != this)
                {
                    ipc_message header_message = new ipc_message();
                    header_message.message = "query";
                    header_message.ip_source = this.ipaddress;
                    header_message.port_source = this.port;
                    if (p.thread != null)
                    {
                        while (p.thread.IsAlive) ;
                    }
                    p.thread = new Thread(new ParameterizedThreadStart(p.send_query));
                    p.thread.Start(header_message);
                    while (!p.thread.IsAlive) ;
                }
            }
            foreach (Peer p in this.peers)
            {
                if (p.thread != null)
                {
                    while (p.thread.IsAlive) ;
                }
            }
            return 1;
        }

        public int join()
        {
            //
            //send join message
            //
            foreach (Peer p in this.peers)
            {
                if (p != this && p.state != State.disconnected)
                {
                    ipc_message header_message = new ipc_message();
                    header_message.message = "join";
                    header_message.ip_source = this.ipaddress;
                    header_message.port_source = this.port;

                    if (p.thread != null)
                    {
                        while (p.thread.IsAlive) ;
                    }
                    p.thread = new Thread(new ParameterizedThreadStart(p.send_message));
                    p.thread.Start(header_message);
                    while (!p.thread.IsAlive) ;
                }
            }
            foreach (Peer p in this.peers)
            {
                if (p.thread != null)
                {
                    while (p.thread.IsAlive) ;
                }
            }

            //
            //Send files
            //
            int leastReplicatedChunk;
            int require_chunks;
            List<Peer> online_peers = new List<Peer>();
            foreach (Peer p in this.peers)
            {
                if (p.state == State.connected && p != this)
                {
                    online_peers.Add(p);
                    if (p.status == null)
                    {
                        this.query_peer(p);
                    }
                }

            }

            for (int i = 0; i < this.status.numberOfFiles(); i++)
            {
                do
                {
                    require_chunks = 0;
                    foreach (Peer p in online_peers)
                    {
                        if (p.state == State.connected && p != this)
                        {
                            this.query_peer(p);
                            this.status.updateMinimumReplicationLevel(i, online_peers);
                            leastReplicatedChunk = this.status.minimumReplicationNeeded(i, online_peers, p);
                            if (leastReplicatedChunk != -1)
                            {
                                require_chunks++;
                                ipc_message header_message = new ipc_message();

                                header_message.file_name = status.file_name[i];
                                header_message.file_hash = status.file_hash[i];
                                header_message.number_of_chunks = status.number_of_chunks[i];
                                header_message.chunk_number = leastReplicatedChunk;
                                header_message.partial_chunk = false;

                                if (status.fractionPresentLocally(i) < 1)
                                {
                                    header_message.partial_chunk = true;
                                }
                                else
                                {
                                    if (File.Exists("files/" + header_message.file_name))
                                    {
                                        FileInfo fi = new FileInfo("files/" + header_message.file_name);
                                        if (fi.Length < (status.number_of_chunks[i] - 1) * Constants.chunkSize)
                                        {
                                            header_message.partial_chunk = true;
                                        }
                                    }
                                }
                                if (p.thread != null)
                                {
                                    while (p.thread.IsAlive) ;
                                }
                                p.thread = new Thread(new ParameterizedThreadStart(p.send_chunk));
                                p.thread.Start(header_message);
                                status.update_peer(p, header_message, leastReplicatedChunk);
                            }
                        }
                    }
                }
                while (require_chunks != 0);
                Console.WriteLine("next file: {0}", i);
            }
            return Constants.errOK; // Unknown error
        }

        public int leave()
        {
            foreach (Peer p in this.peers)
            {
                if (p != this && p.state == State.connected)
                {
                    ipc_message header_message = new ipc_message();
                    header_message.message = "leave";
                    header_message.ip_source = this.ipaddress;
                    header_message.port_source = this.port;

                    if (p.thread != null)
                    {
                        while (p.thread.IsAlive) ;
                    }
                    p.send_message(header_message);
                }
                if (p.state == State.connected)
                {
                    p.state = State.unknown;
                }
            }

            return Constants.errOK;
        }

        public int query_peer(Peer p)
        {
            if (p != this)
            {
                ipc_message header_message = new ipc_message();
                header_message.message = "query";
                header_message.ip_source = this.ipaddress;

                if (p.thread != null)
                {
                    while (p.thread.IsAlive) ;
                }
                p.thread = new Thread(new ParameterizedThreadStart(p.send_query));
                p.thread.Start(header_message);
                while (!p.thread.IsAlive) ;
                while (p.thread.IsAlive) ;

                return Constants.errOK;
            }

            return Constants.errUnknownWarning;
        }

        public void push_files(object peer)
        {
            int leastReplicatedChunk;
            int require_chunks;
            Peer p = (Peer)peer;
            List<Peer> online_peers = new List<Peer>();
            online_peers.Add(p);

            //Send files
            for (int i = 0; i < this.status.numberOfFiles(); i++)
            {
                do
                {
                    require_chunks = 0;

                    if (p.state == State.connected && p != this)
                    {
                        this.query_peer(p);
                        this.status.updateMinimumReplicationLevel(i, online_peers);
                        leastReplicatedChunk = this.status.minimumReplicationNeeded(i, online_peers, p);
                        if (leastReplicatedChunk != -1)
                        {
                            require_chunks++;
                            ipc_message header_message = new ipc_message();

                            header_message.file_name = status.file_name[i];
                            header_message.file_hash = status.file_hash[i];
                            header_message.number_of_chunks = status.number_of_chunks[i];
                            header_message.chunk_number = leastReplicatedChunk;
                            header_message.partial_chunk = false;

                            if (status.fractionPresentLocally(i) < 1)
                            {
                                header_message.partial_chunk = true;
                            }
                            else
                            {
                                if (File.Exists("files/" + header_message.file_name))
                                {
                                    FileInfo fi = new FileInfo("files/" + header_message.file_name);
                                    if (fi.Length < (status.number_of_chunks[i] - 1) * Constants.chunkSize)
                                    {
                                        header_message.partial_chunk = true;
                                    }
                                }
                            }

                            if (p.thread != null)
                            {
                                while (p.thread.IsAlive) ;
                            }
                            p.thread = new Thread(new ParameterizedThreadStart(p.send_chunk));
                            p.thread.Start(header_message);
                            status.update_peer(p, header_message, leastReplicatedChunk);
                        }
                    }
                }
                while (require_chunks != 0);
            }
        }

        public void send_message(object message)
        {
            ipc_message header_message = (ipc_message)message;
            Byte[] chunk = new byte[Constants.chunkSize];

            if (this.is_connected())
            {
                Console.WriteLine("SEND MESSAGE WARNING");
            }

            if (connect() != Constants.errCannotConnect)
            {
                this.state = State.connected;
                header_message.message_type = message_type.message;

                MemoryStream struct_size = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(struct_size, header_message);
                _network_stream.Write(BitConverter.GetBytes(struct_size.Length), 0, BitConverter.GetBytes(struct_size.Length).Length);
                formatter.Serialize(_network_stream, header_message);

                _network_stream.ReadByte();

                disconnect();
            }

        }

        public void send_query(object message)
        {
            int i = 0;
            int bytes_read = 0;
            ipc_message header_message = (ipc_message)message;
            Byte[] r_message = new byte[Constants.chunkSize];

            if (this.is_connected())
            {
                Console.WriteLine("SEND QUERY WARNING");
            }

            if (connect() != Constants.errCannotConnect)
            {
                header_message.message_type = message_type.message;

                MemoryStream struct_size = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(struct_size, header_message);
                _network_stream.Write(BitConverter.GetBytes(struct_size.Length), 0, BitConverter.GetBytes(struct_size.Length).Length);
                formatter.Serialize(_network_stream, header_message);

                _network_stream.ReadByte();

                do
                {
                    i = _network_stream.Read(r_message, bytes_read, r_message.Length - bytes_read);
                    bytes_read += i;
                } while (i != 0);

                MemoryStream ms = new MemoryStream(r_message);
                this.status = (Status)formatter.Deserialize(ms);

                disconnect();
            }
        }

        public void send_chunk(object message)
        {
            ipc_message header_message = (ipc_message)message;
            Byte[] chunk = new byte[Constants.chunkSize];
            FileStream file_stream;

            if (this.is_connected())
            {
                Console.WriteLine("SEND CHUNK WARNING");
            }
            if (connect() != Constants.errCannotConnect)
            {

                header_message.ip_source = this.ipaddress;
                header_message.message_type = message_type.data;

                MemoryStream struct_size = new MemoryStream();
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(struct_size, header_message);
                _network_stream.Write(BitConverter.GetBytes(struct_size.Length), 0, BitConverter.GetBytes(struct_size.Length).Length);
                formatter.Serialize(_network_stream, header_message);

                _network_stream.ReadByte();

                int i = 0;
                if (header_message.partial_chunk == true)
                {
                    file_stream = new FileStream("files/partial_chunks/" + header_message.file_hash + "_" + header_message.file_name + "_" + header_message.chunk_number.ToString().PadLeft((header_message.number_of_chunks.ToString().Length), '0') + "_" + header_message.number_of_chunks, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    i = file_stream.Read(chunk, 0, Constants.chunkSize);
                }
                else
                {
                    file_stream = new FileStream("files/" + header_message.file_name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                    for (int c = header_message.chunk_number; c >= 0; c--)
                    {
                        i = file_stream.Read(chunk, 0, Constants.chunkSize);
                    }
                }
                file_stream.Close();

                Console.WriteLine("CLIENT: Sending Chunk: {0}", header_message.chunk_number);
                _network_stream.Write(chunk, 0, i);

                disconnect();
            }
        }

        private bool is_connected()
        {
            return _is_connected;
        }

        private int disconnect()
        {
            _network_stream.Close();
            _client.Close();
            _is_connected = false;

            return 1;
        }

        private int connect()
        {
            try
            {
                _client = new TcpClient(ipaddress.ToString(), (int)port);
            }
            catch (ArgumentException)
            {
                Console.WriteLine("Argument Exception: {0}");
                return Constants.errCannotConnect; // Cannot connect to anything; fatal error
            }
            catch (SocketException)
            {
                Console.WriteLine("Socket Exception, Server offline: {0}");
                return Constants.errCannotConnect; // Cannot connect to anything; fatal error
            }
            _network_stream = _client.GetStream();
            _is_connected = true;
            return Constants.errOK;
        }
    }

    // Status is the class that you populate with status data on the state
    // of replication in this peer and its knowledge of the replication
    // level within the system.
    // The thing required in the Status object is the data as specified in the private section
    // The methods shown are examples of methods that we may implement to access such data
    // You will need to create methods to populate the Status data.
    [Serializable]
    class Status
    {
        public int numFiles = 0;                                                          // The number of files currently in the system, as viewed by this peer
        public float[] local = new float[Constants.maxFiles];                         // The fraction of the file present locally 
        // (= chunks on this peer/total number chunks in the file)
        public float[] system = new float[Constants.maxFiles];                        // The fraction of the file present in the system 
        // (= chunks in the system/total number chunks in the file)
        // (Note that if a chunk is present twice, it doesn't get counted twice;
        // this is simply intended to find out if we have the whole file in
        // the system; given that a file must be added at a peer, think about why
        // this number would ever not be 1.)
        public int[] leastReplication = new int[Constants.maxFiles];                  // Sum by chunk over all peers; the minimum of this number is the least 
        // replicated chunk, and thus represents the least level of replication
        // of the file
        public float[] weightedLeastReplication = new float[Constants.maxFiles];      // Sum all chunks in all peers; divde this by the number of chunks in the
        // file; this is the average level of replication of the file
        public string[] file_hash = new string[Constants.maxFiles];
        public string[] file_name = new string[Constants.maxFiles];
        public int[] number_of_chunks = new int[Constants.maxFiles];
        public long[] file_size = new long[Constants.maxFiles];
        public List<int>[] chunks_present = new List<int>[Constants.maxFiles];

        public Status()
        {
            int i = 0;
            int c = 0;
            int match = 0;
            DirectoryInfo di = new DirectoryInfo("files");
            FileInfo[] fi = di.GetFiles();

            foreach (FileInfo file in fi)
            {
                //
                //Required
                //
                numFiles++;
                local[i] = 1;
                system[i] = 1;
                leastReplication[i] = 0;
                weightedLeastReplication[i] = 1;

                file_hash[i] = Constants.md5_hash(file.FullName);
                file_name[i] = file.Name;
                file_size[i] = file.Length;
                number_of_chunks[i] = (int)Math.Ceiling(((double)file.Length) / ((double)Constants.chunkSize));
                chunks_present[i] = new List<int>();

                for (c = 0; c < number_of_chunks[i]; c++)
                {
                    chunks_present[i].Add(c);
                }

                i++;
            }

            di = new DirectoryInfo("files/partial_chunks");
            fi = di.GetFiles();

            foreach (FileInfo file in fi)
            {
                match = 0;
                for (c = 0; c < numFiles; c++)
                {
                    if (file.Name.Contains(file_hash[c]))
                    {
                        match = 1;
                        if (!chunks_present[c].Contains(Convert.ToInt32(file.Name.Split('_')[2])))
                        {
                            chunks_present[c].Add(Convert.ToInt32(file.Name.Split('_')[2]));
                            local[c] = (chunks_present[c].Count / (float)number_of_chunks[c]);
                            system[c] = (chunks_present[c].Count / (float)number_of_chunks[c]);
                            leastReplication[c] = l_least_replicated_chunk(c);
                            weightedLeastReplication[c] = (chunks_present[c].Count / (float)number_of_chunks[c]);
                        }
                        break;
                    }
                }
                if (match != 1)
                {
                    number_of_chunks[i] = Convert.ToInt32(file.Name.Split('_')[3]);
                    chunks_present[i] = new List<int>();
                    chunks_present[i].Add(Convert.ToInt32(file.Name.Split('_')[2]));
                    file_hash[i] = file.Name.Split('_')[0];
                    file_name[i] = file.Name.Split('_')[1];

                    numFiles++;
                    local[i] = (float)(1 / (float)number_of_chunks[i]);
                    system[i] = (float)(1 / (float)number_of_chunks[i]);
                    if (Convert.ToInt32(file.Name.Split('_')[2]) == 0)
                    {
                        leastReplication[i] = 0;
                    }
                    else
                    {
                        leastReplication[i] = 1;
                    }
                    weightedLeastReplication[i] = (float)(1 / (float)number_of_chunks[i]); ;

                    i++;
                    match = 0;
                }
            }
        }

        public int l_least_replicated_chunk(int fileNumber)
        {
            for (int i = 0; i < number_of_chunks[fileNumber]; i++)
            {
                for (int c = 0; c < chunks_present[fileNumber].Count; c++)
                {
                    if (!chunks_present[fileNumber].Contains(i))
                    {
                        return i;
                    }
                }
            }
            return Constants.errUnknownFatal;
        }

        public int numberOfFiles()
        {
            return numFiles;
        }

        public int update_peer(Peer p, ipc_message message, int chunk_number)
        {
            int match = 0;
            int index = 0;
            foreach (string file_hash in p.status.file_hash)
            {
                if (file_hash == message.file_hash)
                {
                    match = 1;
                    break;
                }
                index++;
            }
            if (match == 1)
            {
                if (!p.status.chunks_present[index].Contains(chunk_number))
                {
                    p.status.chunks_present[index].Add(chunk_number);
                    p.status.local[index] = (float)(p.status.chunks_present[index].Count / (float)p.status.number_of_chunks[index]);
                    Console.WriteLine("SERVER: Fraction Present1:{0}", p.status.local[index]);
                }
            }
            else
            {
                if (p.status.chunks_present[p.status.numFiles] == null)
                {
                    p.status.chunks_present[p.status.numFiles] = new List<int>();
                    p.status.chunks_present[p.status.numFiles].Add(chunk_number);
                    p.status.file_hash[p.status.numFiles] = message.file_hash;
                    p.status.file_name[p.status.numFiles] = message.file_name;
                    p.status.number_of_chunks[p.status.numFiles] = (int)message.number_of_chunks;
                    p.status.local[p.status.numFiles] = (float)(1 / (float)p.status.number_of_chunks[p.status.numFiles]);
                    p.status.numFiles++;
                    Console.WriteLine("SERVER: Fraction Present2:{0}", p.status.local[p.status.numFiles]);
                }
            }
            Console.WriteLine("ADDING CHUNK TO LIST: {0}", chunk_number);
            return 1;
        }

        // Use -1 to indicate if the file requested is not present
        public float fractionPresentLocally(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return local[fileNumber];
        }

        // Use -1 to indicate if the file requested is not present in the system
        public float fractionPresent(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return system[fileNumber];
        }

        // Use -1 to indicate if the file requested is not present in the system
        public int minimumReplicationLevel(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return leastReplication[fileNumber];
        }

        public void updateMinimumReplicationLevel(int fileNumber, List<Peer> peers)
        {
            int leastReplicatedChunk = 0;
            int leastNumberOfCopies = -1;
            int index = 0;

            for (int i = 0; i < number_of_chunks[fileNumber]; i++)
            {
                int peersWithChunk = 0;
                foreach (Peer peer in peers)
                {
                    if (peer.state == State.connected)
                    {
                        foreach (string file_hash in peer.status.file_hash)
                        {
                            if (file_hash == this.file_hash[fileNumber])
                            {
                                if (peer.status.chunks_present[index].Contains(i))
                                {
                                    peersWithChunk++;
                                }
                            }
                            index++;
                        }
                        index = 0;
                    }
                }
                if (chunks_present[fileNumber].Contains(i)) // Add data for this peer
                {
                    peersWithChunk++;
                }
                if (leastNumberOfCopies > peersWithChunk || leastNumberOfCopies == -1)
                {
                    leastNumberOfCopies = peersWithChunk;
                    leastReplicatedChunk = i;
                }
            }
            //Console.WriteLine("CLIENT: LEAST REPLICATED CHUNK:{0}",leastReplicatedChunk);
            leastReplication[fileNumber] = leastReplicatedChunk;
        }

        public int minimumReplicationNeeded(int fileNumber, List<Peer> peers, Peer p)
        {
            int leastReplicatedChunk = -1;
            int leastNumberOfCopies = -1;
            int index = 0;
            int index_of_hash = 0;
            int match = 0;

            foreach (string file_hash in p.status.file_hash)
            {
                if (file_hash == this.file_hash[fileNumber])
                {
                    match = 1;
                    break;
                }
                index_of_hash++;
            }

            for (int i = 0; i < number_of_chunks[fileNumber]; i++)
            {
                int peersWithChunk = 0;
                foreach (Peer peer in peers)
                {
                    if (peer.state == State.connected)
                    {
                        foreach (string file_hash in peer.status.file_hash)
                        {
                            if (file_hash == this.file_hash[fileNumber])
                            {
                                if (peer.status.chunks_present[index].Contains(i))
                                {
                                    peersWithChunk++;
                                }
                            }
                            index++;
                        }
                        index = 0;
                    }
                }
                if (chunks_present[fileNumber].Contains(i)) // Add data for this peer
                {
                    peersWithChunk++;
                    if (leastNumberOfCopies > peersWithChunk || leastNumberOfCopies == -1)
                    {
                        if (match == 0 || !p.status.chunks_present[index_of_hash].Contains(i))
                        {
                            leastNumberOfCopies = peersWithChunk;
                            leastReplicatedChunk = i;
                        }
                    }
                }
            }

            Console.WriteLine("CLIENT: LRCN by {0}:{1} : {2}", p.ipaddress, p.port, leastReplicatedChunk);
            return leastReplicatedChunk;
        }

        // Use -1 to indicate if the file requested is not present in the system
        public float averageReplicationLevel(int fileNumber)
        {
            if (fileNumber >= numberOfFiles())
            {
                return Constants.errUnknownFatal;
            }
            return weightedLeastReplication[fileNumber];
        }
    }


    // Peers is a dumb container to hold the peers; the number of peers is fixed,
    // but needs to be set up when a peer starts up; feel free to use some other
    // container class, but that class must have a method that allows it to read
    // the peersFile, since otherwise you have no way of having a calling entity 
    // tell your code what the peers are in the system.
    class Peers
    {
        public List<Peer> peers = new List<Peer>();
        public int numPeers = 0;

        // The peersFile is the name of a file that contains a list of the peers
        // Its format is as follows: in plaintext there are up to maxPeers lines,
        // where each line is of the form: <IP address> <port number>
        // This file should be available on every machine on which a peer is started,
        // though you should exit gracefully if it is absent or incorrectly formatted.
        // After execution of this method, the _peers should be present.
        public int initialize(string peersFile)
        {
            if (File.Exists(peersFile))
            {
                try
                {
                    StreamReader file = File.OpenText(peersFile);
                    string s = "";
                    string[] server;

                    while ((s = file.ReadLine()) != null)
                    {
                        server = s.Split(',');
                        try
                        {
                            Peer peer = new Peer();
                            peer.ipaddress = IPAddress.Parse(server[0]);
                            peer.port = uint.Parse(server[1]);
                            peer.peers = this.peers;
                            peer.state = State.unknown;

                            peers.Add(peer);

                            numPeers++;
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("PEERS: IP Address Exception: {0}", e);
                        }
                    }

                    file.Close();
                }
                catch (Exception e)
                {
                    Console.WriteLine("PEERS: File Open Exception: {0}", e);
                }
            }
            else
            {
                Console.WriteLine("File does not exist");
                return Constants.errUnknownFatal;
            }
            return Constants.errUnknownFatal;
        }
    }

    class Constants
    {
        public const int maxPeers = 6;
        public const int chunkSize = 65536;
        public const int maxFiles = 100;

        public const int errOK = 0; // Everything good
        public const int errUnknownWarning = 1; // Unknown warning
        public const int errUnknownFatal = -2; // Unknown error
        public const int errCannotConnect = -3; // Cannot connect to anything; fatal error
        public const int errNoPeersFound = -4; // Cannot find any peer (e.g., no peers in a peer file); fatal
        public const int errPeerNotFound = 5; // Cannot find some peer; warning, since others may be connectable

        public static string md5_hash(string file)
        {
            FileStream file_stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] md5_hash = md5.ComputeHash(file_stream);
            file_stream.Close();

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < md5_hash.Length; i++)
            {
                sb.Append(md5_hash[i].ToString("x2"));
            }

            return sb.ToString();
        }
    }
}

namespace main_test
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            Peers peers = new Peers();
            Status status = new Status();
            IPAddress local_address = IPAddress.Parse("127.0.0.1");
            Peer local_peer = new Peer();

            foreach (IPAddress ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    local_address = ip;
                }
            }

            peers.initialize(@"peers.txt");

            foreach (Peer p in peers.peers)
            {
                if (IPAddress.Equals(p.ipaddress, local_address) && p.port == 10030)
                {
                    local_peer = p;
                    Console.WriteLine(p.ipaddress.ToString());
                    Console.WriteLine(p.port.ToString());
                    break;
                }
            }

            local_peer.status = status;
            new TcpListen(ref peers, ref status, ref local_peer);

            string input;
            int input_enabled = 1;
            while (true)
            {
                if (input_enabled == 1)
                {
                    input = Console.ReadLine();

                    if (input == "join" && input_enabled == 1)
                    {
                        input_enabled = 0;
                        local_peer.join();
                        input_enabled = 1;
                    }
                    else if (input.StartsWith("insert") && input.Contains(" ") && input_enabled == 1)
                    {
                        input_enabled = 0;
                        string[] split = input.Split(' ');
                        local_peer.insert(split[1]);
                        input_enabled = 1;
                    }
                    else if (input == "leave" && input_enabled == 1)
                    {
                        input_enabled = 0;
                        local_peer.leave();
                        input_enabled = 1;
                    }
                    else if (input == "query" && input_enabled == 1)
                    {
                        input_enabled = 0;
                        local_peer.query(ref status);
                        input_enabled = 1;
                    }
                }
            }
        }
    }
}

